
#include <arch/pgtable.h>
#include <arch/pgtable_prot.h>
#include <arch/pgtable_hwdef.h>
#include <base/log.h>
#include <platform.h>
#include <base/mmu.h>

struct addr_marker {
	unsigned long start_address;
	char *name;
};

struct prot_bits {
	unsigned long mask;
	unsigned long val;
	const char *set;
	const char *clear;
};

struct pg_level {
	const struct prot_bits *bits;
	const char *name;
	NX_Size num;
	unsigned long mask;
};

static const struct addr_marker address_markers[] = {
	{ 0, "Identical mapping" },
	{0, "kernel image mapping"},
};

static const struct prot_bits pte_bits[] = {
	{
		.mask	= PTE_VALID,
		.val	= PTE_VALID,
		.set	= " ",
		.clear	= "F",
	}, {
		.mask	= PTE_USER,
		.val	= PTE_USER,
		.set	= "USR",
		.clear	= "   ",
	}, {
		.mask	= PTE_RDONLY,
		.val	= PTE_RDONLY,
		.set	= "ro",
		.clear	= "RW",
	}, {
		.mask	= PTE_PXN,
		.val	= PTE_PXN,
		.set	= "NX",
		.clear	= "x ",
	}, {
		.mask	= PTE_SHARED,
		.val	= PTE_SHARED,
		.set	= "SHD",
		.clear	= "   ",
	}, {
		.mask	= PTE_AF,
		.val	= PTE_AF,
		.set	= "AF",
		.clear	= "  ",
	}, {
		.mask	= PTE_NG,
		.val	= PTE_NG,
		.set	= "NG",
		.clear	= "  ",
	}, {
		.mask	= PTE_CONT,
		.val	= PTE_CONT,
		.set	= "CON",
		.clear	= "   ",
	}, {
		.mask	= PTE_TABLE_BIT,
		.val	= PTE_TABLE_BIT,
		.set	= "   ",
		.clear	= "BLK",
	}, {
		.mask	= PTE_UXN,
		.val	= PTE_UXN,
		.set	= "UXN",
	}, {
		.mask	= PTE_ATTRINDX_MASK,
		.val	= PTE_ATTRINDX(MT_DEVICE_nGnRnE),
		.set	= "DEVICE/nGnRnE",
	}, {
		.mask	= PTE_ATTRINDX_MASK,
		.val	= PTE_ATTRINDX(MT_DEVICE_nGnRE),
		.set	= "DEVICE/nGnRE",
	}, {
		.mask	= PTE_ATTRINDX_MASK,
		.val	= PTE_ATTRINDX(MT_DEVICE_GRE),
		.set	= "DEVICE/GRE",
	}, {
		.mask	= PTE_ATTRINDX_MASK,
		.val	= PTE_ATTRINDX(MT_NORMAL_NC),
		.set	= "MEM/NORMAL-NC",
	}, {
		.mask	= PTE_ATTRINDX_MASK,
		.val	= PTE_ATTRINDX(MT_NORMAL),
		.set	= "MEM/NORMAL",
	}
};

static struct pg_level pg_level[] = {
	{
	}, { /* pgd */
		.name	= "PGD",
		.bits	= pte_bits,
		.num	= NX_ARRAY_SIZE(pte_bits),
	}, { /* pud */
		.name	= "PUD",
		.bits	= pte_bits,
		.num	= NX_ARRAY_SIZE(pte_bits),
	}, { /* pmd */
		.name	= "PMD",
		.bits	= pte_bits,
		.num	= NX_ARRAY_SIZE(pte_bits),
	}, { /* pte */
		.name	= "PTE",
		.bits	= pte_bits,
		.num	= NX_ARRAY_SIZE(pte_bits),
	},
};

static void dump_prot(unsigned long prot, const struct prot_bits *bits,
			NX_Size num)
{
	unsigned i;

	for (i = 0; i < num; i++, bits++) {
		const char *s;

		if ((prot & bits->mask) == bits->val)
			s = bits->set;
		else
			s = bits->clear;

		if (s)
			NX_Printf(" %s", s);
	}
}

static int print_pgtable(unsigned long start, unsigned long end,
		int level, unsigned long val)
{
	static const char units[] = "KMGT";
	unsigned long prot = val & pg_level[level].mask;
	unsigned long delta;
	const char *unit = units;
	int i;

	for (i = 0; i < NX_ARRAY_SIZE(address_markers); i++)
		if (start == address_markers[i].start_address)
			NX_Printf("---[ %s ]---\n", address_markers[i].name);

	if (val == 0)
		return 0;

	NX_Printf("0x%016lx-0x%016lx   ", start, end);

	delta = (end - start) >> 10;
	while (!(delta & 1023) && units[1]) {
		delta >>= 10;
		unit++;
	}

	NX_Printf("%9lu%c %s", delta, *unit,
			pg_level[level].name);

	if (pg_level[level].bits)
		dump_prot(prot, pg_level[level].bits,
				pg_level[level].num);
	NX_Printf("\n");

	return 0;
}

static void walk_pte(PMD_Type *pmdp, unsigned long start, unsigned long end)
{
	PTE_Type *ptep = PTE_OffsetVirt(pmdp, start);
	unsigned long addr = start;

	do {
		print_pgtable(addr, addr + NX_PAGE_SIZE, 4, GetPTE(*ptep));
	} while (ptep++, addr += NX_PAGE_SIZE, addr != end);
}

static void walk_pmd(PUD_Type *pudp, unsigned long start, unsigned long end)
{
	unsigned long next, addr = start;
	PMD_Type *pmdp = PMD_OffsetVirt(pudp, start);
	PMD_Type pmd;

	do {
		pmd = *pmdp;
		next = PMD_AddrEnd(addr, end);

		if (PMD_None(pmd) || PMD_Section(pmd))
			print_pgtable(addr, next, 3, GetPMD(pmd));
		else
			walk_pte(pmdp, addr, next);
	} while (pmdp++, addr = next, addr != end);
}

static void walk_pud(PGD_Type *pgdp, unsigned long start, unsigned long end)
{
	unsigned long next, addr = start;
	PUD_Type *pudp = PUD_OffsetVirt(pgdp, start);
	PUD_Type pud;

	do {
		pud = *pudp;
		next = PUD_AddrEnd(addr, end);

		if (PUD_None(pud) || PUD_Section(pud))
			print_pgtable(addr, next, 2, GetPUD(pud));
		else
			walk_pmd(pudp, addr, next);

	} while (pudp++, addr = next, addr != end);
}

static void walk_pgd(PGD_Type *pgd, unsigned long start, unsigned long end)
{
	unsigned long next, addr = start;
	PGD_Type *pgdp;
	PGD_Type pgd_entry;

	pgdp = PGD_OffsetRaw(pgd, start);

	do {
		if (!pgdp)
			return;
		pgd_entry = *pgdp;
		next = PGD_AddrEnd(addr, end);

		if (PGD_None(pgd_entry))
			print_pgtable(addr, next, 1, GetPGD(pgd_entry));
		else
			walk_pud(pgdp, addr, next);
	} while (pgdp++, addr = next, addr != end);
}

static void pg_level_init(void)
{
	unsigned int i, j;

	for (i = 0; i < NX_ARRAY_SIZE(pg_level); i++)
		if (pg_level[i].bits)
			for (j = 0; j < pg_level[i].num; j++)
				pg_level[i].mask |= pg_level[i].bits[j].mask;
}

void DumpPageTable(PGD_Type *pgd, NX_Addr virAddr, NX_Size size)
{
	static int dumpInit = 0;

	if (!dumpInit)
	{
		dumpInit = 1;
		pg_level_init();
	}
	
	walk_pgd(pgd, virAddr, virAddr + size);
}
